package at.jku.ssw.fortress.ui.assistant;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.eclipse.swt.graphics.Image;

import at.jku.ssw.fortress.ui.ImageConstants;
import at.jku.ssw.fortress.ui.ParentVisitor;
import at.jku.ssw.fortress.ui.ParserDriver;
import at.jku.ssw.fortress.ui.SignatureVisitor;
import at.jku.ssw.fortress.ui.VoidVisitor;
import at.jku.ssw.fortress.util.ImageManager;

import com.sun.fortress.nodes.AbsFnDecl;
import com.sun.fortress.nodes.AbsObjectDecl;
import com.sun.fortress.nodes.AbsTraitDecl;
import com.sun.fortress.nodes.AbsVarDecl;
import com.sun.fortress.nodes.Component;
import com.sun.fortress.nodes.Decl;
import com.sun.fortress.nodes.FnDecl;
import com.sun.fortress.nodes.LValueBind;
import com.sun.fortress.nodes.NatParam;
import com.sun.fortress.nodes.Node;
import com.sun.fortress.nodes.NodeAbstractVisitor;
import com.sun.fortress.nodes.ObjectDecl;
import com.sun.fortress.nodes.Param;
import com.sun.fortress.nodes.StaticParam;
import com.sun.fortress.nodes.TraitDecl;
import com.sun.fortress.nodes.TypeParam;
import com.sun.fortress.nodes.VarDecl;

public class ContextSensitiveAssist {

	private static class Context {
		public final Node node;

		public Context(Node node) {
			this.node = node;
		}
	}

	private static class ContextSearch extends NodeAbstractVisitor<Context> {

		private final int offset;

		public static Context NO_CONTEXT = new Context(null);

		public ContextSearch(int offset) {
			this.offset = offset;
		}

		public Context Context(Node n) {
			return new Context(n);
		}

		@Override
		public Context forComponent(Component x) {
			int start = x.getSpan().getBegin().getOffset();
			int end = x.getSpan().getEnd().getOffset();
			for (Decl y : x.getDecls()) {
                Context c = y.accept(this);
                if (c != NO_CONTEXT) {
                    return c;
                }
            }
			if (start <= offset && offset <= end) {
				return Context(x);
			}
			return NO_CONTEXT;
		}

		@Override
		public Context forTraitDecl(TraitDecl x) {
			for (Decl y : x.getDecls()) {
				Context c = y.accept(this);
				if (c != NO_CONTEXT) {
					return c;
				}
			}
			return forLeaf(x);
		}

		@Override
		public Context forAbsTraitDecl(AbsTraitDecl x) {
			return NO_CONTEXT;
		}

		@Override
		public Context forObjectDecl(ObjectDecl x) {
			for (Decl y : x.getDecls()) {
				Context c = y.accept(this);
				if (c != NO_CONTEXT) {
					return c;
				}
			}
			return forLeaf(x);
		}

		@Override
		public Context forAbsObjectDecl(AbsObjectDecl x) {
			return NO_CONTEXT;
		}

		@Override
		public Context forVarDecl(VarDecl x) {
			return NO_CONTEXT;
		}

		@Override
		public Context forAbsVarDecl(AbsVarDecl x) {
			return NO_CONTEXT;
		}

		@Override
		public Context forFnDecl(FnDecl x) {
			return forLeaf(x);
		}

		@Override
		public Context forAbsFnDecl(AbsFnDecl x) {
			return NO_CONTEXT;
		}

		private Context forLeaf(Node x) {
			int start = x.getSpan().getBegin().getOffset();
			int end = x.getSpan().getEnd().getOffset();
			if (start <= offset && offset < end) {
				return new Context(x);
			}
			return NO_CONTEXT;
		}
	}

	//TODO: we probably made the superclass change incorrectly
	// was VoidVisitor, and all visit*/for* methods should return void, not Object.
	// Superclass should probably be NodeAbstractVisitor_void
	
	private static class ProposalCollector extends  NodeAbstractVisitor<Object> {

		private class Proposer extends NodeAbstractVisitor<Object> {
			private static final String DOC = "<b>Documentation</b>: Unfortunately the fortress parser currently does not extract any documentation from source files";

			private ImageManager imageManager = new ImageManager();

			private Image TRAIT = imageManager.getImage(ImageConstants.ICON_TRAIT);

			private Image OBJECT = imageManager.getImage(ImageConstants.ICON_OBJECT);

			private Image PUBLIC = imageManager.getImage(ImageConstants.ICON_PUBLIC);

			private Image TYPEPARAM = imageManager.getImage(ImageConstants.ICON_TYPEPARAM);

			private Image LOCAL = imageManager.getImage(ImageConstants.ICON_LOCAL);

			@Override
			public Object forTraitDecl(TraitDecl x) {
				propose(x);
				return this;
			}

			@Override
			public Object forFnDecl(FnDecl x) {
				propose(x);
				return this;
			}

			@Override
			public Object forObjectDecl(ObjectDecl x) {
				propose(x);
                return this;
			}

			@Override
			public Object forParam(Param x) {
				propose(x);
				return super.forParam(x);
			}

			@Override
			public Object forVarDecl(VarDecl x) {
				propose(x);
				return super.forVarDecl(x);
			}

			@Override
			public Object forTypeParam(TypeParam x) {
				propose(x.getName().getText(), SignatureVisitor.signature(x), TYPEPARAM);
                return super.forTypeParam(x);
			}

			@Override
			public Object forNatParam(NatParam x) {
				propose(x.getName().getText(), SignatureVisitor.signature(x), TYPEPARAM);
                return this;
			}

			private void propose(Component x) {
				propose(x.getName().getText());
			}

			private void propose(TraitDecl x) {
				propose(x.getName().getText(), SignatureVisitor.signature(x), TRAIT);
			}

			private void propose(ObjectDecl x) {
				propose(x.getName().getText(), SignatureVisitor.signature(x), OBJECT);
			}

			private void propose(FnDecl x) {
				propose(x.getSelfName(), SignatureVisitor.signature(x), PUBLIC);
			}

			private void propose(VarDecl x) {
			    for (LValueBind y: x.getLhs()) {
			        propose(y.getName().getText(), SignatureVisitor.signature(x), PUBLIC);  
			    }
			}

			private void propose(Param x) {
				propose(x.getName().getText(), SignatureVisitor.signature(x), LOCAL);
			}

			private void propose(String p) {
				proposals.add(new Proposal(p, null, null));
			}

			private void propose(String p, String desc, Image image) {
				proposals.add(new Proposal(p, desc, image, p, DOC));
			}
		}

		private Proposer p = new Proposer();

		private final int offset;

		private final List<Proposal> proposals = new ArrayList<Proposal>();

		private final Map<Node, Node> parents;

		public ProposalCollector(int offset, Map<Node, Node> parents) {
			this.offset = offset;
			this.parents = parents;
		}

		@Override
		public Object forTraitDecl(TraitDecl x) {
			for (Decl y : x.getDecls()) {
				y.accept(p);
			}
			for (StaticParam y : x.getStaticParams()) {
					y.accept(p);
			}
			parents.get(x).accept(this);
            return this;
		}

		@Override
		public Object forObjectDecl(ObjectDecl x) {
			for (Decl y : x.getDecls()) {
				y.accept(p);
			}
            for (StaticParam y : x.getStaticParams()) {
                y.accept(p);
            }
			parents.get(x).accept(this);
            return this;
		}

		@Override
		public Object forFnDecl(FnDecl x) {
			for (Param y : x.getParams()) {
				y.accept(p);
			}
            for (StaticParam y : x.getStaticParams()) {
                    y.accept(p);
            }
			parents.get(x).accept(this);
            return this;
		}

		@Override
		public Object forVarDecl(VarDecl x) {
			parents.get(x).accept(this);
            return this;
		}

		@Override
		public Object forComponent(Component x) {
			for (Decl y : x.getDecls()) {
				y.accept(p);
			}
            return this;
		}

		public List<Proposal> proposals() {
			return proposals;
		}

	}

	public List<Proposal> computeCompletionProposals(String code, int offset) {
		try {
			// FIXME MGA determine fileName
			Node ast = ParserDriver.getAST(code, "???.fss");
			if (ast != null) {
				Context context = searchContext(ast, offset);
				if (context.node != null) {
					ParentVisitor pv = new ParentVisitor();
					ast.accept(pv);
					ProposalCollector pc = new ProposalCollector(offset, pv.parents());
					context.node.accept(pc);
					return pc.proposals();
				}
			}
		} catch (Throwable e) {
			e.printStackTrace();
		}
		return new ArrayList<Proposal>();
	}

	private Context searchContext(Node ast, int offset) {
		ContextSearch s = new ContextSearch(offset);
		return ast.accept(s);
	}

}
